/*
 * Copyright (C) 2010 Zaizi Limited.
 *
 * This file is part of Zaizi's Alfresco FISE integration
 * 
 * Alfresco FISE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco FISE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco FISE. If not, see <http://www.gnu.org/licenses/>.
 */
package org.zaizi.fiseiks.alfresco.policies;

import java.util.concurrent.ThreadPoolExecutor;

import org.alfresco.repo.content.ContentServicePolicies;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TransactionListener;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.zaizi.fiseiks.alfresco.model.FiseIksModel;
import org.zaizi.fiseiks.alfresco.service.FiseIksPublishService;

/**
 * Fise policy to convert content into text and post to Fise server for enhancement. 
 * Triggered on onContentUpdatePolicy and onAddAspect if aspect is fise_enhanced.
 * 
 * @author apillai
 * @author efoncubierta
 *
 */
public class FiseIksEnhancePolicy implements ContentServicePolicies.OnContentUpdatePolicy, NodeServicePolicies.OnAddAspectPolicy
{
    private Log logger = LogFactory.getLog(FiseIksEnhancePolicy.class);

    private static final String KEY_NEW_CONTENT = FiseIksEnhancePolicy.class.getName() + ".new";
    
    private ServiceRegistry services;
    private NodeService nodeService;
    private TransactionService transactionService;
    private FiseIksPublishService fiseService;
    
    private TransactionListener transactionListener;
    private PolicyComponent policyComponent;
    private ThreadPoolExecutor threadExecuter;
    
    /**
     * Class initialization
     */
    public void init()
    {
        this.policyComponent.bindClassBehaviour(ContentServicePolicies.OnContentUpdatePolicy.QNAME, 
                FiseIksModel.ASPECT_FISE_ENHANCED, 
                new JavaBehaviour(this, "onContentUpdate", NotificationFrequency.TRANSACTION_COMMIT));
        this.policyComponent.bindClassBehaviour(NodeServicePolicies.OnAddAspectPolicy.QNAME, 
        		FiseIksModel.ASPECT_FISE_ENHANCED, 
                new JavaBehaviour(this, "onAddAspect", NotificationFrequency.TRANSACTION_COMMIT));
        
        transactionListener = new NewContentTransactionListener();
    }
    
    /*
     * (non-Javadoc)
     * @see org.alfresco.repo.node.NodeServicePolicies.OnAddAspectPolicy#onAddAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
	@Override
	public void onAddAspect(NodeRef nodeRef, QName aspectQname)
	{
		if(this.nodeService.exists(nodeRef) && aspectQname.equals(FiseIksModel.ASPECT_FISE_ENHANCED))
		{
			logger.debug("Content updated with fise aspect");
		}
		else
		{
            logger.debug("Content aspect is not " + FiseIksModel.ASPECT_FISE_ENHANCED.toPrefixString());
            return;
		}
			
		AlfrescoTransactionSupport.bindListener(transactionListener);
        AlfrescoTransactionSupport.bindResource(KEY_NEW_CONTENT, nodeRef);
	}

	/*
	 * (non-Javadoc)
	 * @see org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy#onContentUpdate(org.alfresco.service.cmr.repository.NodeRef, boolean)
	 */
    @Override
    public void onContentUpdate(NodeRef nodeRef, boolean newContent)
    {
        if (this.nodeService.exists(nodeRef) && this.nodeService.hasAspect(nodeRef, FiseIksModel.ASPECT_FISE_ENHANCED))
        {
            logger.debug("Content updated with fise aspect");
        }
        else
        {
            logger.debug("Content updated WITHOUT fise aspect");
            return;
        }
        
        Runnable runnable = new PublishToFise(nodeRef);
        threadExecuter.execute(runnable);
    }
    
    /**
     * Inject Alfresco's service registry
     * 
     * @param services Alfresco's service registry
     */
    public void setServiceRegistry(ServiceRegistry services)
    {
        this.services = services;
        this.transactionService = this.services.getTransactionService();
        this.nodeService = this.services.getNodeService();
    }
    
    /**
     * Inject Alfresco's policy component
     * 
     * @param policyComponent Alfresco's policy component
     */
    public void setPolicyComponent(PolicyComponent policyComponent)
    {
        this.policyComponent = policyComponent;
    }
    
    /**
     * Inject Alfresco's thread executer
     * 
     * @param threadExecuter Alfresco's thread executer
     */
    public void setThreadExecuter(ThreadPoolExecutor threadExecuter)
    {
        this.threadExecuter = threadExecuter;
    }

    /**
     * Inject FISE publish service
     * @param fiseService FISE publish service
     */
	public void setFiseService(FiseIksPublishService fiseService) {
		this.fiseService = fiseService;
	}
	
	/**
     * Thread that call FISE service
     * 
     * @author efoncubierta
     *
     */
    private class PublishToFise implements Runnable
    {
        NodeRef nodeRef;
        
        /**
         * Constructor
         * 
         * @param nodeRef Node reference
         */
        public PublishToFise(NodeRef nodeRef)
        {
            this.nodeRef = nodeRef;
        }
    
        /*
         * (non-Javadoc)
         * @see java.lang.Runnable#run()
         */
        @Override
        public void run()
        {
            final RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
            txnHelper.setMaxRetries(1);
            
            final RetryingTransactionCallback<String> callback = new RetryingTransactionCallback<String>()
            {
                @Override
                public String execute() throws Throwable
                {
                    fiseService.postContent(nodeRef);
                    return null;
                }
            };
            
            try
            {
            	AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>() 
    			{
    				public Object doWork() throws Exception
    				{
    					return txnHelper.doInTransaction(callback, false, true);
    	            }
    			}, AuthenticationUtil.SYSTEM_USER_NAME);
                
            } 
            catch (Exception e) 
            {
                logger.warn("Unable to convert content to text and publish to FISE", e);
            }
        }
    }
	
    /**
     * Listener for new content transaction
     * 
     * @author efoncubierta
     *
     */
    private class NewContentTransactionListener extends TransactionListenerAdapter
    {
    	/*
    	 * (non-Javadoc)
    	 * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit()
    	 */
        @Override
        public void afterCommit()
        {
            NodeRef nodeRef = (NodeRef) AlfrescoTransactionSupport.getResource(KEY_NEW_CONTENT);
            if (nodeRef != null)
            {
            	Runnable runnable = new PublishToFise(nodeRef);
                threadExecuter.execute(runnable);
            }

        }
    }
}
